/*
 * Copyright (c) 2012, Marcin Czekaj
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see LICENSE.TXT for a description.
 */
package pl.edu.uj.fais.graph.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections15.Transformer;

import pl.edu.uj.fais.graph.elements.Link;
import pl.edu.uj.fais.graph.elements.Node;
import pl.edu.uj.fais.graph.elements.ProxyNode;
import pl.edu.uj.fais.mode.GraphMode;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Hypergraph;
import edu.uci.ics.jung.graph.SetHypergraph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.io.GraphIOException;
import edu.uci.ics.jung.io.graphml.EdgeMetadata;
import edu.uci.ics.jung.io.graphml.GraphMLReader2;
import edu.uci.ics.jung.io.graphml.GraphMetadata;
import edu.uci.ics.jung.io.graphml.GraphMetadata.EdgeDefault;
import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata;
import edu.uci.ics.jung.io.graphml.NodeMetadata;

public class HyperGraphReader {
    
    private GraphMLReader2<Hypergraph<Node, Link>, Node, Link> reader;
    private Transformer<GraphMetadata, Hypergraph<Node, Link>> graphTransformer;
    private Transformer<NodeMetadata, Node> vertexTransformer;
    private Transformer<EdgeMetadata, Link> edgeTransformer;
    private Transformer<HyperEdgeMetadata, Link> hyperEdgeTransformer;
    
    private boolean isDirected;
    private boolean isHypergraph;
    
    public HyperGraphReader(File file) throws FileNotFoundException,
                                      GraphIOException {
        initTransformers();
        initReader(file);
    }
    
    public Graph<Node, Link> getGraph() throws GraphIOException {
        
        Hypergraph<Node, Link> hyperGraph = reader.readGraph();
        
        if (isHypergraph) {
            return toGraph(hyperGraph);
        } else {
            return (Graph<Node, Link>) hyperGraph;
        }
    }
    
    public GraphMode getGraphMode() {
        if (isHypergraph) {
            return GraphMode.HYPERGRAPH;
        } else {
            return isDirected ? GraphMode.DIRECTED : GraphMode.UNDIRECTED;
        }
    }
    
    private Graph<Node, Link> toGraph(Hypergraph<Node, Link> hyperGraph) {
        
        Graph<Node, Link> graph = new UndirectedSparseGraph<>();
        
        for (Node node : hyperGraph.getVertices()) {
            graph.addVertex(node);
        }
        
        for (Link link : hyperGraph.getEdges()) {
        	Set<Pair<Node>> pairs = new HashSet<>();
            
            List<Node> nodes = new ArrayList<>(hyperGraph.getIncidentVertices(link));
            
            // TODO allow one vertex
            if (nodes.size() <= 1) {
                continue; // no self loops allowed
            }
            
            // no parallel links allowed
            boolean areConnected = false;
            for (int i = 0; i < nodes.size(); i++) {
                for (int j = 0; j < nodes.size(); j++) {
                    if (i != j) {
                        
                        Node a = nodes.get(i);
                        Node b = nodes.get(j);
                        
                        Pair<Node> pair = new Pair<>(a, b);
                        if (pairs.contains(pair)) {
                            areConnected = true;
                        } else {
                            pairs.add(pair);
                        }
                    }
                }
            }
            
            if (!areConnected) {
                
                ProxyNode proxy = new ProxyNode();
                graph.addVertex(proxy);
                
                for (Node node : nodes) {
                    graph.addEdge(new Link(proxy), node, proxy);
                }
            }
        }
        
        return graph;
    }
    
    private void initTransformers() {
        
        graphTransformer = new Transformer<GraphMetadata, Hypergraph<Node, Link>>() {
            
            @Override
            public Hypergraph<Node, Link> transform(GraphMetadata metadata) {
                
                isDirected = (metadata.getEdgeDefault() == EdgeDefault.DIRECTED);
                
                if (metadata.getHyperEdgeMap().size() > 0) {
                    isHypergraph = true;
                    return new SetHypergraph<Node, Link>();
                } else {
                    if (isDirected) {
                        return new DirectedSparseGraph<Node, Link>();
                    } else {
                        return new UndirectedSparseGraph<Node, Link>();
                    }
                }
            }
        };
        
        vertexTransformer = new Transformer<NodeMetadata, Node>() {
            
            private NodeFactory factory = new NodeFactory();
            
            @Override
            public Node transform(NodeMetadata metadata) {
                
                String id = metadata.getId();
                
                Node node = factory.getNode(id);
                
                Map<String, String> attributes = metadata.getProperties();
                
                if (attributes.containsKey("label")) {
                    String label = attributes.get("label");
                    node.setLabel(label);
                }
                
                if (attributes.containsKey("position")) {
                    String[] xy = attributes.get("position").split(";");
                    if (xy.length == 2) {
                        double x = Double.parseDouble(xy[0]);
                        double y = Double.parseDouble(xy[1]);
                        node.setPosition(x, y);
                    }
                }
                
                return factory.getNode(id);
            }
        };
        
        edgeTransformer = new Transformer<EdgeMetadata, Link>() {
            
            @Override
            public Link transform(EdgeMetadata edge) {
                return new Link();
            }
        };
        
        hyperEdgeTransformer = new Transformer<HyperEdgeMetadata, Link>() {
            
            @Override
            public Link transform(HyperEdgeMetadata metadata) {
                return new Link();
            }
        };
    }
    
    private void initReader(File file) throws FileNotFoundException {
        BufferedReader fileReader = new BufferedReader(new FileReader(file));
        reader = new GraphMLReader2<>(fileReader,
                                      graphTransformer,
                                      vertexTransformer,
                                      edgeTransformer,
                                      hyperEdgeTransformer);
    }
}
